﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;

namespace SQLTouch.Service.DataContracts
{
    [DataContract]
    public class QueryData
    {
        private sealed class QueryDataValueComparer : IEqualityComparer<QueryData>
        {
            public bool Equals(QueryData x, QueryData y)
            {
                //Check whether the compared objects reference the same data. 
                if (Object.ReferenceEquals(x, y)) return true;

                //Check whether any of the compared objects is null. 
                if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
                    return false;

                return
                    x.Id == y.Id &&
                    x.TableName == y.TableName &&
                    x.SelectList.SequenceEqual(y.SelectList) &&
                    x.PredicateList.SequenceEqual(y.PredicateList) &&
                    x.GroupByList.SequenceEqual(y.GroupByList) &&
                    x.OrderByList.SequenceEqual(y.OrderByList);
            }

            public int GetHashCode(QueryData obj)
            {
                return obj.Id.GetHashCode() ^ obj.TableName.GetHashCode();
            }
        }

        private static readonly IEqualityComparer<QueryData> valueComparer = new QueryDataValueComparer();

        [DataMember]
        private readonly Guid id;
        [DataMember]
        private readonly Guid databaseId;
        [DataMember]
        private readonly TableNameData tableName;
        [DataMember]
        private readonly List<SelectData> selectList;
        private ReadOnlyCollection<SelectData> selectListReadOnly;
        [DataMember]
        private readonly List<PredicateData> predicateList;
        private ReadOnlyCollection<PredicateData> predicateListReadOnly;
        [DataMember]
        private readonly List<GroupByData> groupByList;
        private ReadOnlyCollection<GroupByData> groupByListReadOnly;
        [DataMember]
        private readonly List<OrderByData> orderByList;
        private ReadOnlyCollection<OrderByData> orderByListReadOnly;

        private QueryData()
        {

        }

        public QueryData(
            Guid id, 
            Guid databaseId, 
            TableNameData tableName,
            IEnumerable<SelectData> selectList,
            IEnumerable<PredicateData> predicateList = null,
            IEnumerable<GroupByData> groupByList = null, 
            IEnumerable<OrderByData> orderByList = null)
        {
            this.id = id;
            this.databaseId = databaseId;
            this.tableName = tableName;
            this.selectList = selectList.ToList();
            this.predicateList = predicateList.ToList();
            this.groupByList = groupByList.ToList();
            this.orderByList = orderByList.ToList();
        }

        public static IEqualityComparer<QueryData> ValueComparer
        {
            get { return QueryData.valueComparer; }
        }

        public Guid Id
        {
            get { return id; }
        }

        public Guid DatabaseId
        {
            get { return databaseId; }
        } 

        public TableNameData TableName
        {
            get { return tableName; }
        }

        public ReadOnlyCollection<SelectData> SelectList
        {
            get
            {
                if (selectListReadOnly == null)
                {
                    selectListReadOnly = new ReadOnlyCollection<SelectData>(selectList);
                }
                return selectListReadOnly;
            }
        }

        public ReadOnlyCollection<PredicateData> PredicateList
        {
            get
            {
                if (predicateListReadOnly == null)
                {
                    predicateListReadOnly = new ReadOnlyCollection<PredicateData>(predicateList);
                }
                return predicateListReadOnly;
            }
        }

        public ReadOnlyCollection<GroupByData> GroupByList
        {
            get
            {
                if (groupByListReadOnly == null)
                {
                    groupByListReadOnly = new ReadOnlyCollection<GroupByData>(groupByList);
                }
                return groupByListReadOnly;
            }
        }

        public ReadOnlyCollection<OrderByData> OrderByList
        {
            get
            {
                if (orderByListReadOnly == null)
                {
                    orderByListReadOnly = new ReadOnlyCollection<OrderByData>(orderByList);
                }
                return orderByListReadOnly;
            }
        }

        private static string GetSQL(PredicateData predicate)
        {
            string opString;
            switch (predicate.Op)
            {
                case PredicateOperator.Equals:
                    opString = "=";
                    break;
                case PredicateOperator.NotEqual:
                    opString = "<>";
                    break;
                case PredicateOperator.Less:
                    opString = "<";
                    break;
                case PredicateOperator.Greater:
                    opString = ">";
                    break;
                case PredicateOperator.LessEqual:
                    opString = "<=";
                    break;
                case PredicateOperator.GreaterEqual:
                    opString = ">=";
                    break;
                default:
                    throw new NotSupportedException(predicate.Op.ToString());
            }
            return predicate.Left + opString + predicate.Right;
        }

        private static string GetSQL(GroupByData groupBy)
        {
            return "[" + groupBy.Column + "]";
        }

        private static string GetSQL(OrderByData orderBy)
        {
            return "[" + orderBy.Column + "] " + (orderBy.IsAscending ? "asc" : "desc");
        }

        private static string GetSQL(SelectData select)
        {
            switch (select.Aggregate)
            {
                case AggregateTypeData.None:
                    return "[" + select.Column + "]";
                case AggregateTypeData.Count:
                    return "count(*)";
                default:
                    return select.Aggregate.ToString() + "([" + select.Column + "])";
            }
        }

        public string GetSQL()
        {
            StringBuilder builder = new StringBuilder();

            builder.Append("select ");
            if (selectList.Count > 0)
            {
                builder.Append(GetSQL(selectList[0]));
                for (int i = 1; i < selectList.Count; i++)
                {
                    builder.Append(", " + GetSQL(selectList[i]));
                }
            }
            builder.AppendLine();

            builder.AppendLine("from " + tableName.ToString());

            if (predicateList.Count > 0)
            {
                builder.Append("where ");
                builder.Append(GetSQL(predicateList[0]));
                for (int i = 1; i < predicateList.Count; i++)
                {
                    builder.Append(" and " + GetSQL(predicateList[i]));
                }
                builder.AppendLine();
            }

            if (groupByList.Count > 0)
            {
                builder.Append("group by ");
                builder.Append(GetSQL(groupByList[0]));
                for (int i = 1; i < groupByList.Count; i++)
                {
                    builder.Append(", " + GetSQL(groupByList[i]));
                }
                builder.AppendLine();
            }

            if (orderByList.Count > 0)
            {
                builder.Append("order by ");
                builder.Append(GetSQL(orderByList[0]));
                for (int i = 1; i < orderByList.Count; i++)
                {
                    builder.Append(", " + GetSQL(orderByList[i]));
                }
                builder.AppendLine();
            }

            string result = builder.ToString();
            return result;
        }
    }
}
